Android-MVVM之LiveData和ViewModel

前言

之前归纳了databinding,说到底databinding也只是承载实体数据和UI进行关联,以及它们之间互通,转换。但是呢们开发过程中需要有中间人来控制逻辑,连接后台数据到绑定数据。这个时候ViewModel加上DataBinding以及LiveData就可以很好的组成一种架构模式。我们还是在了解LiveData和ViewModel之后,再结合之前的DataBinding来说MVVM吧。

LiveData

老规矩,首先附上官方文档地址:LiveData

看看官方怎么定义LiveData的:

LiveData是一个可观察对象持有类,不同于平常的可观察对象,LiveData是有生命周期感知的,意味着它会关心app组件的生命周期,比如Activity,fragment,service。这种感知特性保证LiveData只会在生命周期活跃的时候进行观察更新。

LiveData考虑将观察者,在生命周期为startedresumed下保持活跃。LiveData仅仅会通知活跃的观察者进行更新,不活跃的观察者不会收到任何通知。

你可以注册观察者并且给他配对实现了LifecycleOwner 的接口的对象。在这层关系下,观察者会在Lifecycle对象进入destory时被移除,这个对于fragmentactivity非常有用,它可以很安全的观察livedata对象,在fragmentactivity生命周期销毁时,它们会立即解除订阅,不会产生内存泄漏。

LiveData优点

  • 确保UI和数据状态匹配

    通过观察者模式,livedata会在生命周期发生改变通知观察者,通过书写固定更新UI代码,在数据发生改变时触发,而不是手动在数据改变时改变UI。

  • 没有内存泄漏

    观察者会被绑定到Lifecycle对象,在关联的生命周期被销毁时,观察者会被清理。

  • activity停止不会崩溃

    如果观察者处于不活跃状态,比如activity在后台运行,观察者不会收到livedata的事件

  • 不需要手动管理生命周期

    UI组件只会观察数据,不会停止和恢复观察,这些都是交由livedata自动管理。因为livedata可以感知

  • 保持数据最新

    如果生命周期是不活跃的,它将在变为活跃是获取最新的数据。例如activity从后台来到前台将会收取最新的数据。

  • 适配配置更改

    activity和fragment因为配置更改而重建,它们会收到最新的数据

  • 共享资源

    使用单例模式继承LiveData,并且包裹系统服务,以便能够在在全局使用,一旦连接到系统服务,任何观察者都可以查看livedata。

使用LiveData

一般来讲是要声明依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dependencies {
def lifecycle_version = "2.0.0"

// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
// alternatively - just LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
// AndroidX libraries use this lightweight import for Lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"

annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // For Kotlin use lifecycle-reactivestreams-ktx

// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}

但是呢,我在AS3.4的基础上打开Databing后,这些LiveData和ViewModel都有了。。这些组件也是几年前就有了就,估计因为现在是9012年的原因,直接都给你集成好了。

一般来说是这样的步骤:

  • 在ViewModel中声明创键LiveData对象
  • 在UI控制器中,比如activity,fragment中创键观察者Observer对象,实现onChange逻辑
  • 通过在LiveData上调用oberve关联ObserverLiveData,需要传入lifecycleOwner,一般是在UI控制中执行

如果oberver和livedata之间没有关联lifecycleOwner,则默认考虑这个observer总是处于活跃状态,即总是接受通知发生调用。你可以通过removeOberver()移除它

当livedata数据发生改变,将会触发附着在生命周期上活跃的oberver

livedata允许在控制器中订阅它,并且触发进行UI变化

创键LiveData对象

LiveData一个可以包装对象,包括了集合,一个LiveData通常是在ViewModel中声明的,同时提供getter方法。

1
2
3
4
5
6
7
8
9
10
11
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> currentName;
public MutableLiveData<String> getCurrentName() {
if (currentName == null) {
currentName = new MutableLiveData<String>();
}
return currentName;
}
// Rest of the ViewModel...
}

确保在ViewModel中存储UI的LiveData对象,第一,避免activity和fragment过度膨胀,它们的职责应该是负责UI显示而不是保存数据状态,第二、将LiveData实例和activity,fragment进行解耦,并且允许在配置发生改变时依然存活

观察LiveData对象

在大多数情况下,在app组件的onCreate方法中开启LiveData观察是非常适合的,原因如下:

  • onResume调用时不会产生冗余调用在
  • 确保activity,fragment拥有数据,在处于活跃状态立即展示它,一旦进入started状态,立即收取最近的数据

触发活跃状态下的oberver,例外,从非活跃转为活跃也会触发,如果第二次从非活跃到活跃,只有上次情况下的值发生改变了才会触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class NameActivity extends AppCompatActivity {

private NameViewModel model;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
model = ViewModelProviders.of(this).get(NameViewModel.class);
// Create the observer which updates the UI.
final Observer<String> nameObserver = new Observer<String>() {
@Override
public void onChanged(@Nullable final String newName) {
// Update the UI, in this case, a TextView.
nameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.getCurrentName().observe(this, nameObserver);
}
}

数据改变,触发监听

更新LiveData对象

通过setValue(T)postValue来更新数据,触发监听,更新UI

setValue只能在主线程中更新数据

postValue可以在子线程中,子线程中用它就对了

1
2
3
4
5
6
7
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String anotherName = "John Doe";
model.getCurrentName().setValue(anotherName);
}
});

Room中使用LiveData

Room支持可观察的查询,返回LiveData对象,通过LiveData,以及设置观察者,保持UI数据和数据库同步,具体以后看Room的学习吧

LiveData使用协程

支持kotlin的协程(呃呃,这个我还不是很会😭)

扩展LiveData

LiveData会根据oberver生命周期宿主是否是STARTEDRESUME来认定oberver是否处于活跃和非活跃状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class StockLiveData extends LiveData<BigDecimal> {
private StockManager stockManager;

private SimplePriceListener listener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};

public StockLiveData(String symbol) {
stockManager = new StockManager(symbol);
}

@Override
protected void onActive() {
stockManager.requestPriceUpdates(listener);
}

@Override
protected void onInactive() {
stockManager.removeUpdates(listener);
}
}
  • onActive 当LiveData拥有活跃的oberver,可以开始监听数据,调用
  • onInActive LiveData没有活跃的oberver,不会触发监听,调用

通过扩展,可以加入自己的监听器

这些obsever会随着生命周期变化,比如活跃状态,消亡状态

通过单例进行分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class StockLiveData extends LiveData<BigDecimal> {
private static StockLiveData sInstance;
private StockManager stockManager;

private SimplePriceListener listener = new SimplePriceListener() {
@Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};

@MainThread
public static StockLiveData get(String symbol) {
if (sInstance == null) {
sInstance = new StockLiveData(symbol);
}
return sInstance;
}

private StockLiveData(String symbol) {
stockManager = new StockManager(symbol);
}

@Override
protected void onActive() {
stockManager.requestPriceUpdates(listener);
}

@Override
protected void onInactive() {
stockManager.removeUpdates(listener);
}
}

然后你可以在fragment使用

1
2
3
4
5
6
7
8
public class MyFragment extends Fragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
StockLiveData.get(symbol).observe(this, price -> {
// Update the UI.
});
}
}

变换LiveData

当你想要在发送通知前改变LiveData的值,或者你需要基于原来的LiveData返回一个另一LiveDataLifeCycle包里面提供了这样的工具类Transformations

map

1
2
3
4
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});

switchMap

1
2
3
4
5
6
private LiveData<User> getUser(String id) {
...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

变换是惰性的,只有在在正处于观察和返回数据时进行计算。

合并多个LiveData源

MediatorLiveData是一个包装了LiveData数据源集合的LiveData的,本质上是个包装类,也就是说完全可以当初LiveData使用,内部的话可以添加多个LiveData数据源,并且设置监听,一旦某数据源发生数据改变,可以触发监听,同时可以设置自身的数据,从而表现为一个LiveData。简单点说就是可以接受多个数据源,从而发生改变表现为一个数据源。

总结

总的来讲LiveData对象,就是一个具有生命周期感知的,持有可观察数据,不仅仅局限于UI。使用它可以在MVVM的结构中使得ViewModel不依赖View这一层从而完成View的变化。

View->ViewModel->Model

ViewModel

定义:在生命周期中,用于存储管理UI关联的数据。ViewModel允许在配置发生改变时存活

缘由:

Android framework管理者activity,fragment的生命周期,Android framework 也许会因为某些失控的事件和操作来进行销毁,重建activity,fragment。在发生这种情况下时候,瞬时的UI数据就会丢失,在之前的情况下我们会通过onSaveInstanceState去保存某些数据,以便下次重建时onCreate进行数据恢复,但是这个解决方式只适用于一些轻量的序列化数据,像那些Bitmap就不适用了。

再者,UI控制器会经常性去做一些异步调用,并且有可能返回数据,为了防止内存泄漏,UI控制器需要管理这些调用,在UI控制器销毁时去清理。这种方式需要大量的维护,并且在配置发生改变,某些需要重建的资源对象在重建时会消耗大量资源。

对于UI控制器来说,它的责任应该是UI展示,UI交互,处理和系统的交流比如权限请求。将过多的责任分配给UI控制器会导致代码臃肿。尝试将一些责任分配出去,也会便于测试。

实现ViewModel

架构组件提供ViewModel来帮助UI控制器承担UI数据贮备的职责,并且它会在配置改变时自动保存,下次恢复时立马可用。

这个例子是用于加载用户列表数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
loadUsers();
}
return users;
}

private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}

在UI控制器中监听数据变化

1
2
3
4
5
6
7
8
9
10
11
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.

MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}

ViewModel不允许持有View引用、Lifecycle以及任何持有了activity context的对象的引用即UI无关

生命周期

ViewModel的作用域是通过Lifecycle通过ViewModelProvider传递的,ViewModel会保留在内存中,直到Lifecycle的作用域消失,比如,activity的finish,fragment的detach,可以见下图

ViewModel生命周期

通常来讲实在第一次请求onCreate创键ViewModel,过程中可能多次onCreate,但是在第一次请求ViewModel直到activity finished and destroyed 之间都是存在的。

在Fragment间共享数据,通信

通常一个activity会包含多个fragment,然而多个fragment之间会涉及相关联的数据以及操作,fragment之间需要通信,简单的解决方法是通过定义接口进行回调,但是很繁琐,每个都写那不得烦死,还得绑定,解绑,生命周期可见性,各种考虑。通过共享得ViewModel可以很好的解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

public void select(Item item) {
selected.setValue(item);
}

public LiveData<Item> getSelected() {
return selected;
}
}


public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}

public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}

两个fragment共同处于一个activity,当然viewmodel也是同一个。

优点

  • activity不需要了解任何事情,甚至通信内容
  • fragment只需要了解和Viewmodel之间的契约,其它的不需要了解,其中之一的fragment消失了,另一个还是会正常工作
  • 每个fragment都有各自的生命周期,它们之间互不影响,如果一个fragment被替换了,UI还是会正常工作。

使用ViewModel替换loader进行加载

以前使用loader来同步UI和数据库的数据

现在这样用

ViewModel中使用协程

待学习

总结

随着数据变得复杂,考虑将数据加载分离出去,当当当,这一步走完不就是MVVM了吗?哈哈

MVVM

其实从DataBind到LiveData,再到ViewModel就差不多是MVVM的雏形了

M:Model层,用于实际的数据加载,只负责加载数据

V:View层,负责UI展示,交互

VM:ViewMdoel层,负责View的数据,连通View和Model,负责获取Model的数据,转化为UI的数据,当然也包括了逻辑处理

如果感觉VM层太繁重,可以继续分离一层Presenter来处理逻辑,亦或者分离一层Mediator来处理验证

View -> ViewModel -> Model 现在的依赖单向,通过LiveData和双向绑定,ViewModel不需要依赖View了。

现在的架构变成这样了,简洁明了